package asteroids.model;

import asteroids.Util;
import asteroids.CollisionListener;
import be.kuleuven.cs.som.annotate.*;

/**
 * @invar	the x-co and y co must be valid at all times
 * 			| isValidPosition(this.getPositionX()) && isValidPosition(this.getPositionY())
 * @invar	the radius must be valid at all time
 * 			| isValidRadius(this.getRadius())
 * @invar	the speed must be less than the maximum speed of flyingObjects
 * 			| this.getVelocity() < maxSpedd
 * 
 * @version	1.0
 * @author	Thijs Peirelinck & Wouter Parys
 *
 */
public class FlyingObject {
	private Vector position;
	private Vector velocity;
	private double radius;
	private final double maxSpeed;
	private final double minimalRadius;
	private final double mass;
	private World world;
	private boolean isTerminated;
	
	/**
	 * 
	 * @param	positionX
	 * 			The x-co of this new flyingobject
	 * @param	positionY
	 * 			the y-co of this new flyingobject
	 * @param	velocityX
	 * 			the x-co of the velocity-vector
	 * @param	velocityY
	 * 			the y-co of the velocity-vector
	 * @param	mass
	 * 			the mass of this new flyingobject
	 * @param	minimalRadius
	 * 			the minimal radius this object has
	 * @post	the maxspeed of a flyingobject is 300000
	 * 			| this.getMaxspeed() == 300000
	 * @post	| the minimalradius is equal to the given parameter minimalRadius
	 * 			this.getMinimalRadius == minimalRadius
	 * @effect	the flyingobject has an velocity
	 * 			| setVelocity(velocityX,velocityY)
	 * @effect	the flyingobject has a valid position
	 * 			| setPosition(positionX, positionY)
	 * @effect	the flyingobject is not terminated
	 * 			| this.isTerminated() == false;
	 */
	public FlyingObject(double positionX, double positionY, double velocityX, double velocityY, double mass, double minimalRadius) {
		this.maxSpeed = 300000;
		this.minimalRadius = minimalRadius;
		this.mass=mass;
		setVelocity(velocityX, velocityY);
		setPosition(positionX,positionY);
		this.isTerminated = false;
		
	}
	
	/**
	 * Returns the current state of this flyingObject
	 * 
	 * @return 	true if this flyingObject is terminated
	 * 			|| isTerminated
	 */
	@Basic @Raw
	public boolean isTerminated() {
		return isTerminated;
	}
	
	@Basic @Raw
	public World getWorld() {
		return world;
	}
	
	/**
	 * sets the world of this flyingObject
	 * @param	world
	 * @post 	this.getWorld == world
	 */
	@Raw
	public void setWorld(World world) {
		this.world = world;
	}
	
	/**
	 * checks wether this flyinObject has a world
	 * @return	world != null
	 */
	@Raw
	public boolean hasWorld() {
		return (world != null);
	}
	
	@Basic @Raw
	public double getMass() {
		return mass;
	}
	
	@Basic @Raw
	public double getPositionX() {
		return this.getPositionVector().getX();
	}
	
	@Basic @Raw
	public double getPositionY() {
		return this.getPositionVector().getY();
	}
		
	/**
	 * Changes the x- and y-coordinate of this flyingObject.
	 * 
	 * @param	x
	 * 			The given x-coordinate for the flyingObject.
	 * @param	y 
	 * 			The given x-coordinate for the flyingObject.
	 * @throws	IllegalArgumentException
	 * 			At least one of the given x- and y-coordinate is not a valid position for a flyingObject.
	 * 			| isValidPosition(x) || isValidPosition(y)
	 * @throws	IllegalStateException
	 * 			when this object is terminated
	 * 			| isTerminated()
	 * @post	the new position of this flyingObject is the position with the given x and y as coordinates.
	 * 			| (new this).getPositionX() = x && (new this).getPositionY() = y
	 */
	protected void setPosition(double x, double y) throws IllegalArgumentException, IllegalStateException {
		if(isTerminated()) { throw new IllegalStateException(); }
		if(! isValidPosition(x) && ! isValidPosition(y)) {
			throw new IllegalArgumentException();
		}
		else {
			position = new Vector(x,y);
		}
	}
	
	
	/**
	 * Check whether the given coordinate is a valid position for a flyingObject.
	 * 
	 * @param	positionCoordinate
	 * 			The coordinate to check.
	 * @return	true if and only if the given coordinate is a number and is in the range of the class Double.
	 * 			| result == ((!= Double.isNaN(positionCoordinate))
	 * 			|	&& (positionCoordinate <= Double.MAX_VALUE)
	 * 			|	&& (positionCoordinate >= -Double.MIN_VALUE))
	 */
	@Model
	private boolean isValidPosition(double positionCoordinate) {
		return ((! Double.isNaN(positionCoordinate) ) && (positionCoordinate <= Double.MAX_VALUE) && (positionCoordinate >= -Double.MAX_VALUE) );
	}
	
	@Basic
	public Vector getPositionVector() {
		return position;
	}
	
	@Basic @Raw
	public double getVelocityX() {
		return this.getVelocityVector().getX();
	}
	
	@Basic @Raw
	public double getVelocityY() {
		return this.getVelocityVector().getY();
	}
	
	@Basic @Raw
	public double getVelocity() {
		return this.getVelocityVector().norm();
	}
	
	@Basic @Raw
	public Vector getVelocityVector() {
		return velocity;
	}
	
	@Basic @Immutable @Raw
	public double getMaxSpeed() {
		return maxSpeed;
	}
	
	@Basic @Immutable @Raw
	public double getMinimalRadius() {
		return minimalRadius;
	}
	
	/**
	 * This method changes the value of velocityX and velocityY of this flyingObject.
	 * 
	 * @param	velocityX
	 * 			The given velocityX for this flyingObject.
	 * @param	velocityY
	 * 			The given velocityY for this flyingObject.
	 * @post	If the total velocity, calculated with the given velocityX and velocityY,
	 * 			is smaller than the maximum speed of the flyingObject, the new velocityX and new velocityY 
	 * 			are the same as the given parameters.
	 * 			| if(Math.sqrt(Math.pow(this.getVelocityX(), 2)+Math.pow(this.getVelocityY(), 2)) <= this.getMaxSpeed())
	 * 			|	then (new this).getVelocityX() == this.getVelocityX() && (new this).getVelocityY() == this.getVelocityX()
	 * @post	If the total velocity, calculated with the given velocityX and velocityY,
	 * 			exceeds the maximum speed of the flyingObject, new values for velocityX and velocityY are calculated 
	 * 			in the direction of the given velocityX and velocityY. this new values will be the new velocityY an new VelocityY
	 * 			The total velocity will be the same as the maxSpeed of the flyingObject.
	 * 			| if(Math.sqrt(Math.pow(this.getVelocityX(), 2)+Math.pow(this.getVelocityY(), 2)) > this.getMaxSpeed() && velocityX != 0 && velocityY != 0)
	 * 			|	then (new this).getVelocityX() == 
	 * 			|		Math.sqrt(Math.pow(this.getMaxSpeed(), 2)/(Math.pow(this.getVelocityY()/this.getVelocityX(), 2)+1))
	 * 			|	  && (new this).getVelocityY() == 
	 * 			|		Math.sqrt(Math.pow(this.getMaxSpeed(), 2)/(Math.pow(this.getVelocityX()/this.getVelocityY(), 2)+1))
	 * 			| if (velocityX == 0)
	 * 			|   then (new this).getVelocityX() == maxSpeed && (new this).getVelocityY() == 0
	 * 			| if (velocityY == 0)
	 * 			|   then (new this).getVelocityX() == 0 && (new this).getVelocityY() == maxSpeed
	 *  @throws	IllegalStateException
	 * 			when this object is terminated
	 * 			| isTerminated()
	 */
	protected void setVelocity(double velocityX, double velocityY) throws IllegalStateException{
		if(isTerminated()) { throw new IllegalStateException(); }
		if(asteroids.Util.fuzzyLessThanOrEqualTo(Math.sqrt(Math.pow(velocityX,2)+Math.pow(velocityY,2)), maxSpeed)) {
			velocity = new Vector(velocityX, velocityY);
			
		}
		else {
			if (velocityX == 0) {
				velocity = new Vector(0, maxSpeed);
			}
			else if (velocityY == 0) {
				velocity = new Vector(maxSpeed, 0);
			}
			else {
				velocity = new Vector(Math.sqrt(Math.pow(this.maxSpeed, 2)/(Math.pow(velocityY/velocityX, 2)+1)),Math.sqrt(Math.pow(this.maxSpeed, 2)/(Math.pow(velocityX/velocityY, 2)+1)));
			}			
		}
	}
	
	
	@Basic @Immutable @Raw
	public double getRadius() {
		return radius;
	}
	
	/**
	 * This method gives the flyingObject a new radius.
	 * 
	 * @param	radius
	 * 			The given radius for this flyingObject.
	 * @throws 	IllegalArgumentException
	 * 			the given radius is not a valid radius.
	 * 			| !isValidRadius(radius)
	 * @throws	IllegalStateException
	 * 			when this object is terminated
	 * 			| isTerminated()
	 * @post	The new radius of the flyingObject is the same as the given radius.
	 *			| (new this).getRadius() == radius
	 */
	protected void setRadius(double radius) throws IllegalArgumentException, IllegalStateException {
		if(isTerminated()) { throw new IllegalStateException(); }
		if(! isValidRadius(radius)) {
			throw new IllegalArgumentException();
		}
		this.radius = radius;	
	}
	
	/**
	 * Checks wether the given radius is a valid radius
	 * @param radius
	 * @return	(radius > minimalRadius && radius <= Double.MAX_VALUE && !Double.isNaN(radius))
	 */
	@Model
	private boolean isValidRadius(double radius) {
		return (radius > minimalRadius && radius <= Double.MAX_VALUE && !Double.isNaN(radius));
	}

	
	/**
	 * Return the distance between this flyingObject and a given flyingObject.
	 * 
	 * @param 	flyingObject
	 * @throws	OverflowException
	 * 			throws an overflowexception when the distance between the flyingObjects is more than the max value of a double.
	 * 			| this.getPositionVector().getDistanceBetween(flyingObject.getPositionVector) == Double.POSITIVE_INFINITY
	 * @throws	NullPointerException
	 * 			the given flyingObject is the null-reference
	 * 			| flyingObject == null
	 * @return	the distance between this flyingObject and a given flyingObject.
	 * 			| (this.getPositionVector().getDistanceBetween(flyingObject.getPositionVector())) - (this.getRadius()+flyingObject.getRadius())
	 * @throws	IllegalArgumentException
	 *			when this and flyingObject have a world but not the same
	 *			| (!this.hasWorld()) || (!flyingObject.hasWorld())	 
	 */
	public double getDistanceBetween(FlyingObject object) throws exceptions.OverflowException, NullPointerException, IllegalArgumentException {
		if((object.hasWorld() && this.hasWorld()) && (object.getWorld() != this.getWorld())) { throw new IllegalArgumentException();} 
		if(this == object) {
			return -this.getRadius();
		}
		double distance = getPositionVector().getDistanceBetween(object.getPositionVector());
		if(distance == Double.POSITIVE_INFINITY)
			throw new exceptions.OverflowException();
		distance = distance - (this.getRadius()+object.getRadius());
		return distance;
	}
	
	/**
	 * Check whether this flyingObject overlaps with a given flyingObject.
	 * 
	 * @param	object
	 * 			The flyingObject to check the overlap with.
	 * @return	true if the given flyingObject overlaps with this flyingObject. A bullet will
	 * 			never overlap with his source.
	 * 			| if (! (this instanceof Bullet) && ! (object instanceof Bullet) )
	 * 			|	 then 
	 * 			| 		result == (asteroids.Util.fuzzyLessThanOrEqualTo(this.getDistanceBetween(object), 0) && !asteroids.Util.fuzzyEquals(this.getDistanceBetween(object),0))
	 *			| else
	 *			|	 if (this.getSource() == object || object.getSource == this)
	 *			|		then result == false
	 *@throws	IllegalArgumentException
	 *			| when the flyingObject is in an other world
	 *			| this.getWorld() != flyingObject.getWorld()
	 *@throws	IllegalArgumentException
	 *			when this or flyingObject have a world but not the same
	 *			| (!this.hasWorld()) || (!flyingObject.hasWorld())	 
	 */
	public boolean overlap(FlyingObject object) throws IllegalArgumentException {
		if((object.hasWorld() && this.hasWorld()) && (object.getWorld() != this.getWorld())) { throw new IllegalArgumentException();} 
		if (this instanceof Bullet) {
			if (((Bullet)this).getSource() == object) {
				return false;
			}
		}
		else if (object instanceof Bullet) {
			if (((Bullet)object).getSource() == this) {
				return false;
			}
		}
		return (asteroids.Util.fuzzyLessThanOrEqualTo(this.getDistanceBetween(object), 0) && !asteroids.Util.fuzzyEquals(this.getDistanceBetween(object),0)); 
	}
	
	/**
	 * Return the time until this flyingObject and a given flyingObject will collide. 
	 * 
	 * @param   other
	 *          The other flyingObject to collide with.
	 * @throws 	exceptions.OverflowException
	 * 			| throws an overflowexception when some of the calculations become larger than the max value of a double
	 * 			| ! isValidSubtraction(object.getVelocityX(),this.getVelocityX()) 
	 *			| ! isValidSubtraction(object.getVelocityY(),this.getVelocityY())
	 *			| ! isValidSubtraction(object.getPositionX(),this.getPositionX())
	 *			| ! isValidSubtraction(object.getPositionY(),this.getPositionY())	 
	 * @return  The resulting time is not negative and different from Double.NaN
	 *        | Util.fuzzyLeq(0.0,result) && (! Double.isNaN(result))
	 * @return  If the resulting time is finite, the distance between both
	 *          flyingObjectss would be fuzzy equal to zero if they would both move
	 *          during the resulting time.
	 *        | if (result < Double.POSITIVE_INFINITY) then
	 *        |   Util.fuzzyEquals(this.distanceBetween(other,result),0.0)
	 * @return  If the resulting distance is finite, the distance between both flyingObjects
	 *          would be fuzzy different from zero if they would move for a time shorter than the
	 *          resulting time.
	 *        | if (result < Double.POSITIVE_INFINITY) then
	 *        |   for each time in 0.0..result:
	 *        |     if (time < result)
	 *        |       then ! Util.fuzzyEquals(this.distanceBetween(other,time),0.0)
	 * @return  If the resulting time is infinite, this flyingObject is the same as the
	 *          other flyingObject or the distance between both
	 *          flyingObjects would be different from zero for each finite time they would move.
	 *        | if (result == Double.POSITIVE_INFINITY) then
	 *        |   (this == other) ||
	 *        |   (for each time in 0.0..Double.POSITIVE_INFINITY:
	 *        |     if (! Double.isInfinite(time)) then
	 *        |       (! Util.fuzzyEquals(this.distanceBetween(other,time),0.0))
	 *@throws  NullPointerException
	 *          The other flyingObject is not effective.
	 *        | other == null
	 *@throws	IllegalArgumentException
	 *			| when the flyingObject is in an other world
	 *			| this.getWorld() != flyingObject.getWorld()
	 *@throws	IllegalArgumentException
	 *			when this or flyingObject doesn't have a world
	 *			| (!this.hasWorld()) || (!flyingObject.hasWorld())	 
	 */
	public double getTimeToCollision(FlyingObject object) throws exceptions.OverflowException, NullPointerException, IllegalArgumentException {
		if((object.hasWorld() && this.hasWorld()) && (object.getWorld() != this.getWorld())) { throw new IllegalArgumentException();} 
		if(! Vector.isValidVectorSubtraction(object.getVelocityVector(),this.getVelocityVector()) 
				|| ! Vector.isValidVectorSubtraction(object.getPositionVector(),this.getPositionVector()))
				 {
			throw new exceptions.OverflowException();
		}
	
		if(object.getRadius()+this.getRadius() == Double.POSITIVE_INFINITY) {
			throw new exceptions.OverflowException();
		}
		double sigma = object.getRadius()+this.getRadius();
		Vector deltaR = object.getPositionVector().delta(this.getPositionVector());
		Vector deltaV = object.getVelocityVector().delta(this.getVelocityVector());
		double deltaRR = deltaR.scalarProduct(deltaR);
		double deltaVV = deltaV.scalarProduct(deltaV);
		double deltaVR = deltaV.scalarProduct(deltaR);
		
		double d = Math.pow(deltaVR, 2) - (deltaVV)*(deltaRR-Math.pow(sigma, 2));
		if(deltaVR >= 0) {
			return Double.POSITIVE_INFINITY;
		}
		else if(Util.fuzzyLessThanOrEqualTo(d, 0)) {
			return Double.POSITIVE_INFINITY;
		}
		else {
			return (-deltaVR-Math.sqrt(d))/deltaVV;
		}
		
	}
	
	/**
	 * Returns the x- and y-coordinate of the collisionpoint.
	 * 
	 * @param object
	 * @return	the x- and y-co of the collisionposition and null if the flyingObjects never collide
	 * 			| let deltaT be the time to the next collision of this and object
	 * 					detltaT = this.getTimeToCollision(object)
	 * 			| if(deltaT < Double.POSITIVE_INFINITY) then
	 * 				| let deltaRx1 be the x-co of this where it will collide
	 * 					deltaRx1 = this.getPositionX()+deltaT*this.getVelocityX()
	 * 				| let deltaRy1 be the y-co of this where it will collide
	 * 					deltaRy1 = this.getPositionY()+deltaT*this.getVelocityY()
	 * 				| let deltaRx2 be the x-co of object where it will collide
	 * 					deltaRx2 = object.getPositionX()+deltaT*object.getVelocityX()
	 * 				| let deltaRy2 be the y-co of object where it will collide
	 * 					deltaRy2 = object.getPositionY()+deltaT*object.getVelocityY()
	 * 				result == ([0] = deltaRx1+(deltaRx2-deltaRx1)*this.getRadius()/(this.getRadius()+object.getRadius())
	 * 							&& [1] =  deltaRy1+(deltaRy2-deltaRy1)*this.getRadius()/(this.getRadius()+object.getRadius()))
	 * 			| else 
	 * 				| result == null
	 * 									
	 * @throws NullPointerException
	 * 			| throws nullpointerexception when the given flyingObject is the null-reference
	 * 			| flyingObject == null
	 *@throws	IllegalArgumentException
	 *			when this or flyingObject have a world but the world is not the same
	 *			| (!this.hasWorld()) || (!flyingObject.hasWorld())	 
	 */
	public double[] getCollisionPosition(FlyingObject object) throws NullPointerException, IllegalArgumentException {
		if((object.hasWorld() && this.hasWorld()) && (object.getWorld() != this.getWorld())) { throw new IllegalArgumentException();} 
		double[] position = new double[2];
		
		double deltaT = getTimeToCollision(object);
		
		if(deltaT < Double.POSITIVE_INFINITY) {
			double deltaRx1 = this.getPositionX()+deltaT*this.getVelocityX();
			double deltaRy1 = this.getPositionY()+deltaT*this.getVelocityY();
			double deltaRx2 = object.getPositionX()+deltaT*object.getVelocityX();
			double deltaRy2 = object.getPositionY()+deltaT*object.getVelocityY();
			
			position[0] = deltaRx1+(deltaRx2-deltaRx1)*this.getRadius()/(this.getRadius()+object.getRadius());
			position[1] = deltaRy1+(deltaRy2-deltaRy1)*this.getRadius()/(this.getRadius()+object.getRadius());
			
			return position;
		}
		else {
			return null;
		}
	}
	

	/**
	 * move the flyingObject to a new position using a given time-lap, angle and acceleration-parameter.
	 * 
	 * @param	deltaT
	 * 			The time-lap between the old position and the new position.
	 * @post	The flyingObject has moved during a given time-period.
	 * 			| if(delta < 0)
	 * 			|	then (new this).getPositionX() == this.getPositionX() 
	 * 			|		 (new this).getPositionY() == this.getPositionY()
	 * 			| if(deltaT >= 0)
	 * 			|	then (new this).getPositionX() == this.getPositionX()+this.getVelocityX()*deltaT
	 * 			|		 (new this).getPositionY() == this.getPositionY()+this.getVelocityY()*deltaT
	 * @effect	setPosition(getPositionVector().getX() + getVelocityVector().getX()*deltaT,getPositionVector().getY() + getVelocityVector().getY()*deltaT)	
	 * @throws	IllegalStateException
	 * 			when this object is terminated
	 * 			| isTerminated()	
	 */		
	public void move(double deltaT) throws IllegalStateException {
		if(isTerminated()) { throw new IllegalStateException(); }
		if(deltaT < 0) {
			deltaT = 0;
		}
		double X = getPositionVector().getX() + getVelocityVector().getX()*deltaT;
		double Y = getPositionVector().getY() + getVelocityVector().getY()*deltaT;
		setPosition(X,Y);
	}
	
	/**
	 * make this flyingObject collide with another flyingObject
	 * @param 	flyingObject
	 * 			the flyingobject which collide with this object
	 * @param 	collisionListener
	 * 			
	 * @post	this.isTerminated() == true
	 * @effect	flyingObject.terminate()
	 * @throws	IllegalStateException
	 * 			when this object is terminated
	 * 			| isTerminated()
	 */
	public void collide(FlyingObject flyingObject, CollisionListener collisionListener) throws IllegalStateException{
		if(isTerminated()) { throw new IllegalStateException(); }
		this.terminate();
		flyingObject.terminate();
	}
	
	/**
	 * terminate this flyingObject
	 * @post	! this.getWorld.getFlyingObjects().contains(this)
	 * @post	! this.hasWorld()
	 * @post	this.isTerminated() 
	 */
	public void terminate(){
		if (this.hasWorld()){
			this.getWorld().removeFlyingObject(this);
		}
		this.setWorld(null);
		this.isTerminated = true;
	}
	
	/**
	 * let this flyingObject bounce off a another flyingObject
	 * 
	 * @param 	flyingObject
	 * @post	let deltaV be flyingObject.getVelocityVector().delta(this.getVelocityVector())
	 * 			let deltaR be flyingObject.getPositionVector().delta(this.getPositionVector())
	 * 			let J be (2*this.getMass()*flyingObject.getMass()*(deltaV.scalarProduct(deltaR))/(sigma*(this.getMass()+flyingObject.getMass())))
	 * 			let sigma be this.getRadius()+flyingObject.getRadius()
	 * 			(new this).getVelocityX() == this.getVelocityX() + (J*(flyingObject.getPositionX()-this.getPositionX())/sigma)/this.getMass()
	 * 			(new this).getVelocityY() == this.getVelocityY()+(J*(flyingObject.getPositionY()-this.getPositionY())/sigma)/this.getMass()
	 * 			(new flyingObject).getVelocityX == flyingObject.getVelocityX() + (J*(flyingObject.getPositionX()-this.getPositionX())/sigma)/flyingObject.getMass()
	 * 			(new flyingObject).getVelocityY() == flyingObject.getVelocityY()+(J*(flyingObject.getPositionY()-this.getPositionY())/sigma)/flyingObject.getMass()

	 * @throws	IllegalStateException
	 * 			when this object is terminated
	 * 			| isTerminated()
	 */
	public void bounceoff(FlyingObject flyingObject) throws IllegalStateException {
		if(isTerminated()) { throw new IllegalStateException(); }
		double sigma = this.getRadius()+flyingObject.getRadius();
		Vector deltaV = flyingObject.getVelocityVector().delta(this.getVelocityVector());
		Vector deltaR = flyingObject.getPositionVector().delta(this.getPositionVector());
		double J = (2*this.getMass()*flyingObject.getMass()*(deltaV.scalarProduct(deltaR))/(sigma*(this.getMass()+flyingObject.getMass())));
		double Jx = J*(flyingObject.getPositionX()-this.getPositionX())/sigma;
		double Jy = J*(flyingObject.getPositionY()-this.getPositionY())/sigma;
		Vector velocity1 = new Vector(this.getVelocityX()+Jx/this.getMass(),this.getVelocityY()+Jy/this.getMass());
		Vector velocity2 = new Vector(flyingObject.getVelocityX()-Jx/flyingObject.getMass(),flyingObject.getVelocityY()-Jy/flyingObject.getMass());
		this.setVelocity(velocity1.getX(), velocity1.getY());
		flyingObject.setVelocity(velocity2.getX(), velocity2.getY());
	}
	
	/**
	 * let this flyingObject bounce off a vertical wall
	 * 
	 * @post	(new this).getVelocityX() == -this.getVelocityX()
	 * 			(new this).getVelocityY() == this.getVelocityY()
	 * @throws	IllegalStateException
	 * 			when this object is terminated
	 * 			| isTerminated()
	 */
	public void bounceOffVerticalWall() throws IllegalStateException {
		if(isTerminated()) { throw new IllegalStateException(); }
		setVelocity(-this.getVelocityX(), this.getVelocityY());
	}
	
	/**
	 * let this flyingObject bounce off a horizontal wall
	 * 
	 * @post	(new this).getVelocityX() == this.getVelocityX()
	 * 			(new this).getVelocityY() == -this.getVelocityY()
	 * @throws	IllegalStateException
	 * 			when this object is terminated
	 * 			| isTerminated()
	 */
	public void bounceOffHorizontalWall() throws IllegalStateException {
		if(isTerminated()) { throw new IllegalStateException(); }
		setVelocity(this.getVelocityX(), -this.getVelocityY());
	}
	
	/**
	 * returns the time to the collision with the width of the world
	 * 
	 * @return	if (this.getVelocityX() >=0) 
	 * 				then (this.getWorld().getWidth() - this.getRadius() - this.getPositionX())/this.getVelocityX()
	 * 			else ((this.getRadius()-this.getPositionX())/this.getVelocityX())
	 * @throws	NullPointerException
	 * 			this.hasWorld == false
	 */
	public double getTimeToCollisionWidth() throws NullPointerException {
		if (this.getVelocityX()>=0) {
			return (this.getWorld().getWidth() - this.getRadius() - this.getPositionX())/this.getVelocityX();
		}
		else {
			return ((this.getRadius()-this.getPositionX())/this.getVelocityX());
		}
	}
	
	/**
	 * returns the time to collision with the height of the world
	 * @return	if (this.getVelocityY() >= 0) 
	 * 				then (this.getWorld().getHeight() - this.getRadius() - this.getPositionY())/this.getVelocityY()
	 * 			else ((this.getRadius()-this.getPositionY())/this.getVelocityY())
	 */
	public double getTimeToCollisionHeight() {
		if (this.getVelocityY()>=0) {
			return (this.getWorld().getHeight() - this.getRadius() - this.getPositionY())/this.getVelocityY();
		}
		else {
			return ((this.getRadius()-this.getPositionY())/this.getVelocityY());
		}
	}
	
	/**
	 * returns the smallest time to collision with one of the walls
	 * @return	if(this.getTimeToCollisionWidth()<this.getTimeToCollisionHeight())
	 * 				then result == getTimeToCollisionWidth()
	 * 			else result == getTimeToCollisionHeight()
	 */
	public double getTimeToCollision() {
		
		
		if (this.getTimeToCollisionWidth()<this.getTimeToCollisionHeight()) {
			return getTimeToCollisionWidth();
		}
		
		else {
			return getTimeToCollisionHeight();
		}
	}
	
	/**
	 * let this flyingObject bounce of a wall
	 * @post	if(this.getTimeToCollisionWidth()<this.getTimeToCollisionHeight())
	 * 				then (new this).getVelocityX() == -this.getVelocityX()
	 * 					 (new this).getVelocityY() == this.getVelocityY()
	 * 			else (new this).getVelocityX() == this.getVelocityX()
	 * 				 (new this).getVelocityY() == this.getVelocityY()
	 * @throws	IllegalStateException
	 * 			when this object is terminated
	 * 			| isTerminated()
	 */
	public void bounceOffWall() throws IllegalStateException{
		if(isTerminated()) { throw new IllegalStateException(); }
		if (this.getTimeToCollisionWidth()<this.getTimeToCollisionHeight()) {
			this.bounceOffVerticalWall();
		}
		
		else {
			this.bounceOffHorizontalWall();
		}
	}
}
